# 1. The QDataWidgetMapper class allows us to map data from a certain row and column of our model into a widget in Qt.
# The widget can be anything from a combobox, lineEdit or even a custom widget made in the designer or written from scratch.
# This mapping then allows us to use these widgets to modify the data at the corresponding row and column. For example,
# a lineEdit can be used to modify the name of a Node, spinBox for light intensity etc.
# All of this is done while maintaining the sync property of model view programming.
# 2. The properties of each item are placed in something called 'property grids'. The property grids are then embedded into the
# final application. We have created the different property grids in using 'widgets' in the Qt Designer.
# In this design, we have four nodes; Node, Camera, Transform and Light so we need to create four widgets.
# Node - has two properties. Name and Type Info. Name should be 'editable'. Type Info should be 'read only'. We put them in
# lineEdits. Labels have minimumWidth of 75. Type Info lineEdit has readOnly property selected.
# Lights has four properties. Light Intensity, Near Range, Far Range (double spin boxes) and Cast Shadows(chkBox).
# Camera has two properties. Motion blur (chkBox) and Shake Intensity (double spin box)
# Transform has one property. Position (x, y and z)
# View is a visual representation of the data. All the views (.ui files) have been put into the Views folder.
# Models module contain the SceneGraphModel we created earlier.
# Data module contains all our Nodes, i.e. the data.
# Controllers are whatever that drive our application; the logic behind it that hooks everything together.

import sys
from PySide import QtCore, QtGui
import Resources                 # Import the icons file that was made earlier
from loadUiType import *
from XMLHighlighter import *

# import Data and Models
from Data import Node, LightNode, CameraNode, TransformNode, LIGHT_SHAPES
from Models import SceneGraphModel
   
# The uic module contains a method called loadUiType (here I have used a custom user implementation of that module).
# We input the .ui file saved from the Qt Designer to this function. This method returns the form class and the base class,
# which we need to derive from, and create a new window.
# The propeties.ui file will contain a group box containing two horizontal layouts aligned vertically. The vertical layout 
# above will always contain node properties. The lower vertical layout will hold the specialized properties depending upon 
# user selection. The properties.ui will be located on the lower part of our window.
base, form = loadUiType("Views/Window.ui")
nodeBase, nodeForm = loadUiType("Views/NodeEditor.ui")
lightBase, lightForm = loadUiType("Views/LightEditor.ui")
cameraBase, cameraForm = loadUiType("Views/CameraEditor.ui")
transformBase, transformForm = loadUiType("Views/TransformEditor.ui")
propBase, propForm = loadUiType("Views/Editors.ui")


# Let's create a new class that derives from the form and the base class and call that Wnd. It is derived from form and base class.
class Wnd(base, form):
    # In the constructor, we need to call the super class constructor of the base class and pass the parent to it. Then we also
    # need to call the setupUi method that gets generated by the loadUiType method. setupUi method actually initializes all our
    # controls placed in the Qt Designer. 
    def __init__(self, parent=None):
        super(base, self).__init__(parent)
        self.setupUi(self)
        
        self._rootNode = Node("Head")
        childNode0 = TransformNode("apple", self._rootNode)
        childNode1 = LightNode("app",     self._rootNode)
        childNode2 = CameraNode("cat",    self._rootNode)
        childNode3 = TransformNode("dog", self._rootNode)
        childNode4 = LightNode("elephant",     self._rootNode)
        childNode5 = CameraNode("fig",    self._rootNode)
        childNode6 = TransformNode("goat", self._rootNode)
        childNode7 = CameraNode("hat",    self._rootNode)
        childNode8 = LightNode("ink",     self._rootNode)
        childNode9 = LightNode("junk", childNode8)
        childNode10 = CameraNode("kite", childNode8)
        childNode11 = LightNode("light", childNode10)
        
        # Implement the filter
        # Our goal is to type something in the lineEdit and based on that, the uiTree should show the filtered results.
        # For that we have to use the QSortFilterProxyModel class.  So let's create one.        
        self._proxyModel = QtGui.QSortFilterProxyModel()
        # We put our proxy model in between. So instead of hooking the model directly into the view, we will hook the model
        # into the proxy model and then take the proxy model and hook it into the view. Also we made the models as protected
        # members using the self._model notations.
        self._model = SceneGraphModel(self._rootNode, self)   # <------ This is the data model
        self._proxyModel.setSourceModel(self._model) # <------ This is the proxy model
        self._proxyModel.setDynamicSortFilter(True) # To enable dynamic sorting as text is entered
        self._proxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) # To enable case insensitivity
        # ** Setting the user defined sort and filter roles
        # Then in the data method we need to return the appropriate data as the result of sort and filter roles. 
        self._proxyModel.setSortRole(SceneGraphModel.sortRole)
        self._proxyModel.setFilterRole(SceneGraphModel.filterRole)
        # setFilterKeyColumn for filtering a particular column. Column 0 in this case. However note that I must decouple
        # the user filter role from the proxy model (i.e. comment self._proxyModel.setFilterRole(SceneGraphModel.filterRole)
        self._proxyModel.setFilterKeyColumn(0) # *** don't forget to comment out setFilterRole otherwise this will be useless.
        self.uiTree.setModel(self._proxyModel)     # <------ This is the view
        # All we need to do now is to call the setFilterRegExp with the required string.
        # For filtering out data using hardcoded filter string we can use the following code
        # self._proxyModel.setFilterRegExp("Light")
        # But we want to use our lineEdit's (we named the lineEdit, uiFilter) current text. For that we need to use signals
        # and slots provided by the Qt framework.
        # LineEdit emits the signal textChanged when the text is changed by the user. The signal also holds a parameter with the
        # current text. So if we would connect that signal to the setFilterRegExp method, it will automatically set the new filter
        # text whenever we edit the filter field. To connect the textChanged signal to the setFilterRegExp method we use this code.
        # connect() is a static function. First parameter is the signal emitter, second is.. which signal we want to connect i.e.
        # here we have the textChanged signal with type QString. Third is whose the receiver of that signal. Here it is the proxy
        # model and the function from the proxy model is setFilterRegExp.
        QtCore.QObject.connect(self.uiFilter, QtCore.SIGNAL("textChanged(QString)"), self._proxyModel.setFilterRegExp)
        
        # Now let's implement sorting
        # For that we only need to call setSortingEnabled method on our uiTree.
        # Note: It would show an error if we already don't have a proxy model in between the data model and the view. 
        self.uiTree.setSortingEnabled(True)
        # If you need a different logic or process than QSortFilterProxyModel then you should consider subclassing the
        # QtGui.QAbstractProxyModel and implement the behavior you are after.

        # Now instantiate the propertiesEditor class inside our main window class.
        # Create an object of properties editor class with self as the parent.
        self._propEditor = PropertiesEditor(self)
        self.layoutMain.addWidget(self._propEditor)
        # Finally set the model of our propEditor with our proxyModel
        self._propEditor.setModel(self._proxyModel)
        
        # This is how we are connecting the currentChanged signal to our setSelection method.
        # Connecting the signal with our widget. The "owner" of the signal is the self.uiTree.selectionModel. Which signal,
        # we want to connect, the currentChanged signal. Who is gonna receive the parameters, the nodeEditor's setSelection function
        QtCore.QObject.connect(self.uiTree.selectionModel(), QtCore.SIGNAL("currentChanged(QModelIndex, QModelIndex)"), self._propEditor.setSelection)
        
        # We wish to update the Xml in its respective ui whenever the data is changed (i.e. setData method from the
        # models class is called). We do that by using static connect method from the QObject class.
        # Owner: model, Signal name: dataChanged with two parameters (from and to, we are using the same object)
        # Receiver method of the signal: updateXml 
        QtCore.QObject.connect(self._model, QtCore.SIGNAL("dataChanged(QModelIndex, QModelIndex)"),self.updateXml)
    
        # To use the XMLHighlighter, create a QTextEdit (already in the Window.ui). Then create an XMLHighlighter
        # object and pass the uiXml's document object to it.
        # Create the XMLHighlighter object, derived from QSyntaxHighlighter
        highlighter = XMLHighlighter(self.uiXml.document())
        
        # Generate the xml for the first time at the start of the program
        self.updateXml()
    
    # The update Xml method calls the asXml() method in the Data class. The asXml() returns the Xml code of the
    # given hierarchy (by using the recurseXml method). We just need to provide the rootNode to it.
    # Once we have the xml, we can just put it into the uiXml widget in the Window.ui. 
    def updateXml(self):
        print("Updating XML")
        # self.uiXml.setPlainText('<!--I love my watchers-->\n<pyqt4>\n\t<tutor name="yasin"/>\n</pyqt4>')
        xml = self._rootNode.asXml()
        self.uiXml.setPlainText(xml)

# Once we have the form and base class uic modules, then we can create classes that are derived from the form class and the
# base class and then call the setypUi method to initialize our controllers. So we see that we are creating the classes by
# deriving from base and form classes. Then we call setupUi. We do that for all the Forms we made in designer.
 
""" NODE """
class NodeEditor(nodeBase, nodeForm):
    def __init__(self, parent=None):
        super(nodeBase, self).__init__(parent)
        self.setupUi(self)
        # Now, the QDataWidgetMapper has to be used on the classes. We need one WDataWidgetMapper object per class.
        self._dataMapper = QtGui.QDataWidgetMapper()
    # Then we need to set the model that the QDataWidgetMapper will use. That's done by calling setModel. Since our
    # dataMapper is a protected member, we should provide our own method for setting the model.
    # The model may be any model but we since we are setting a proxy model that is why we have named the model variable as
    # proxyModel variable.
    def setModel(self, proxyModel):
        # temporarily store the model then set it
        self._proxyModel = proxyModel
        # The data mapper does not want a proxy model since that is not where the implementation of how to receive the data
        # is. It wants our original data model. Fortunately, to give it that is easy. The proxy model calss has a getter called
        # source model. So on the dataMapper.setModel we will provide the sourceModel, since the data lies in the sourceModel.  
        self._dataMapper.setModel(proxyModel.sourceModel())
        # Once the model is set, we can addMapping. The addMapping method takes two arguments, a specific column and the
        # widget that you want to map to. The widget will correspond to the uiName and uiType here. The data will be retrieved
        # from the column and displayed in the widget. And, if you edit the data in the widget, it will be submitted back to
        # the same model, into the same column. The QDataWidgetMapper uses only the displayRole. If we inspect the data method
        # of our model, we see that for column 0, we return the name of the node and for column 1, we return the typeInfo.
        # Even if we may not want to display the typeInfo in a separate column (by using columnCount of 1, we could still
        # map our widget to the data inside of that column. 
        self._dataMapper.addMapping(self.uiName, 0) # name is in column 0 
        self._dataMapper.addMapping(self.uiTypeInfo, 1) # typeInfo is in column 1
        # The setModel method we just created, should be called in our window class with our data model.
        # For testing, let's select the first item in our model for editing. For that we can use the toFirst() method.
        # Now we can see the first item of our model in the lineEdit widgets, to be edited. However we usually don't do
        # that. We usually edit the item that we selected and not the first item only. 
        # self._dataMapper.toFirst()
    # Let's make a method to set the selected data in the tree view instead of the first column. This method should take two
    # arguments, current selection and old selection. But the nodeEditor.setSelection method does not need the oldSelection
    # parameter anymore because it will receive the selection from our propertiesEditor and not from a QtCore.SIGNAL, which
    # actually sends two QModelIndex-es
    # Remember that QModelIndex-es store valuable information about an item. 
    """ Inputs: QModelIndex """
    def setSelection(self, currentSelection):
        # Get the parent from the current QModelIndex (or the currentSelection)
        parent = currentSelection.parent()
        # Once we have the parent, we need to set it as our root index in our data mapper.
        self._dataMapper.setRootIndex(parent)
        # Finally we need to set the current QModelIndex to the current selection.
        self._dataMapper.setCurrentModelIndex(currentSelection)
        # All that is left now is now is using a signal called currentChange and connect it with our setSelection method. The
        # owner of this signal is the selection model of our tree view. We haven't talked about selection models yet. But you
        # should know that they exist and can be used "to sync the selection of two views".
        # For that, see the window class. The signal is connected in the bottom lines of the window class.
        # Now, whenever we select an item (be it root, parent or child), we can see and edit it using the lineEdit fields.
        # And it works on the children as well. That's because we have set the root of the QDataWidget mapper.
        # We have a working "property grid" which updates with our current selection and is also able to edit the data.

        
""" LIGHT """
class LightEditor(lightBase, lightForm):
    def __init__(self, parent=None):
        super(lightBase, self).__init__(parent)
        self.setupUi(self)
        
        self._dataMapper = QtGui.QDataWidgetMapper()
        
        # Fill the combo box with shapes enumerated list. Keep on adding items until the "End" item is reached.
        for i in LIGHT_SHAPES.names:
            if i != "End":
                self.uiShape.addItem(i)

    def setModel(self, proxyModel):
        self._proxyModel = proxyModel
        self._dataMapper.setModel(proxyModel.sourceModel())
        # The addMapping takes the ui and the column that we want to map to, as arguments. We can also provide an
        # optional parameter (as in the case of uiShape) that give the property of the widget that we want to bind to.
        # Since combo boxes usually don't have a single item and you have to bind to one among many items, the index
        # of the current item is given by the "currentIndex" property 
        self._dataMapper.addMapping(self.uiLightIntensity, 2) 
        self._dataMapper.addMapping(self.uiNear, 3)
        self._dataMapper.addMapping(self.uiFar, 4) 
        self._dataMapper.addMapping(self.uiShadows, 5)
        self._dataMapper.addMapping(self.uiShape, 6, "currentIndex")
        
    """ Inputs: QModelIndex """
    def setSelection(self, currentSelection):
        parent = currentSelection.parent()
        self._dataMapper.setRootIndex(parent)
        self._dataMapper.setCurrentModelIndex(currentSelection)
        
        
""" CAMERA """
class CameraEditor(cameraBase, cameraForm):
    def __init__(self, parent=None):
        super(cameraBase, self).__init__(parent)
        self.setupUi(self)
        
        self._dataMapper = QtGui.QDataWidgetMapper()

    def setModel(self, proxyModel):
        self._proxyModel = proxyModel
        self._dataMapper.setModel(proxyModel.sourceModel())
        
        self._dataMapper.addMapping(self.uiMotionBlur, 2) 
        self._dataMapper.addMapping(self.uiShakeIntensity, 3)
        
    """ Inputs: QModelIndex """
    def setSelection(self, currentSelection):
        parent = currentSelection.parent()
        self._dataMapper.setRootIndex(parent)
        self._dataMapper.setCurrentModelIndex(currentSelection)

        
""" TRANSFORM """
class TransformEditor(transformBase, transformForm):
    def __init__(self, parent=None):
        super(transformBase, self).__init__(parent)
        self.setupUi(self)
        
        self._dataMapper = QtGui.QDataWidgetMapper()
        
    def setModel(self, proxyModel):
        self._proxyModel = proxyModel
        self._dataMapper.setModel(proxyModel.sourceModel())
        
        self._dataMapper.addMapping(self.uiX, 2) 
        self._dataMapper.addMapping(self.uiY, 3)
        self._dataMapper.addMapping(self.uiZ, 4) 
        
    """ Inputs: QModelIndex """
    def setSelection(self, currentSelection):
        parent = currentSelection.parent()
        self._dataMapper.setRootIndex(parent)
        self._dataMapper.setCurrentModelIndex(currentSelection)
        
        
""" Properties Editor """
class PropertiesEditor(propBase, propForm):
    def __init__(self, parent=None):
        super(propBase, self).__init__(parent)
        self.setupUi(self)
        # A protected member to store the proxy model.
        self._proxyModel = None
        
        # Create the editors inside our propery editor class. Note that the editors take a parent argument in the constructor.
        # By putting the properties window as the parent, the editors will be in the properties window.
        self._nodeEditor = NodeEditor(self)
        self._lightEditor = LightEditor(self)
        self._cameraEditor = CameraEditor(self)
        self._transformEditor = TransformEditor(self)
        
        # We want to dock the editors into the vertical layout elements in the UI
        self.layoutNode.addWidget(self._nodeEditor)
        self.layoutSpecs.addWidget(self._lightEditor)
        self.layoutSpecs.addWidget(self._cameraEditor)
        self.layoutSpecs.addWidget(self._transformEditor)
        
        # Hide all the editors except the node editor as required.
        self._lightEditor.setVisible(False)
        self._cameraEditor.setVisible(False)
        self._transformEditor.setVisible(False)
        
    def setModel(self, proxyModel):
        # Store the proxyModel variable to a protected member
        self._proxyModel = proxyModel
        # Calling the setModel method to set the model to our proxyModel.
        self._nodeEditor.setModel(proxyModel) 
        self._lightEditor.setModel(proxyModel) 
        self._cameraEditor.setModel(proxyModel) 
        self._transformEditor.setModel(proxyModel) 

    # Let's make a setSelection method. But we will only delegate the currentSelection to our node class.
    def setSelection(self, currentSelection, oldSelection):
        # The currentSelection is an QModelIndex class object. It is received from the QProxy class which might have sorted
        # everything. However the values it holds might not match our original data model. So we need to map it bact to
        # the source. For that a function called mapToSource is available. It takes a QModelIndex and returns a mapped
        # QModelIndex. So we take the currentSelection which is a QModelIndex, map it to source and retrieve a new QModelIndex.
        currentSelection = self._proxyModel.mapToSource(currentSelection)
        
        node = currentSelection.internalPointer()
        if node is not None:
            typeInfo = node.typeInfo()
            # Depending upon typeInfo, we can show our editors
        if typeInfo == "CameraNode":
            self._lightEditor.setVisible(False)
            self._cameraEditor.setVisible(True)
            self._transformEditor.setVisible(False)
        elif typeInfo == "LightNode":
            self._lightEditor.setVisible(True)
            self._cameraEditor.setVisible(False)
            self._transformEditor.setVisible(False)
        elif typeInfo == "TransformNode":
            self._lightEditor.setVisible(False)
            self._cameraEditor.setVisible(False)
            self._transformEditor.setVisible(True)
        else:
            self._lightEditor.setVisible(False)
            self._cameraEditor.setVisible(False)
            self._transformEditor.setVisible(False)

        # The editors no longer need the old selection parameter because they will receive the selection from a properties
        # editor and not from a QtCore signal which actually sends two QModelIndex-es
        self._nodeEditor.setSelection(currentSelection)
        self._cameraEditor.setSelection(currentSelection)
        self._lightEditor.setSelection(currentSelection)
        self._transformEditor.setSelection(currentSelection)
        


if __name__ == "__main__":
    
    appObject = QtGui.QApplication(sys.argv)
    appObject.setStyle('cleanlooks')
        
    # Create the class and show it
    wnd = Wnd()
    wnd.show()
    
    light = LightNode("lihghhh").attrs()
    
    
    
    # Execute the application
    sys.exit(appObject.exec_())
